
#include <unistd.h>
#include <sys/time.h>
#include <sys/stat.h>
#include "log_impl.h"

namespace trident
{

log_impl_t::log_impl_t()
    : _log_level(LOG_LEVEL_WARN), _log_fmt(LOG_FMT_DATE|LOG_FMT_MSECOND|LOG_FMT_LEVEL),
    _pthread_id(0), _running(false), _timeout(LOG_THREAD_TIMEOUT), _file_max_size(FILE_DEFAULT_MAX_SIZE),
    _current_file_size(0), _current_file_index(0), _current_year(0), _current_month(0), _current_day(0)
{
}

log_impl_t::~log_impl_t()
{
    if (_running)
    {
        stop();
        _running = false;
    }
}

int log_impl_t::init(const string& file_path_,
        const string& file_name_,
        log_level_e log_level_,
        unsigned long file_max_size_,
        int log_fmt_)
{
    _file_path = file_path_;
    _file_name = file_name_;
    _log_level = log_level_;
    _file_max_size = file_max_size_;
    _log_fmt = log_fmt_;

    if (_log_queue.init())
    {
        fprintf(stderr, "log_impl_t::init Failed to init log file\n");

		return -1;
    }

    if (initFileInfo())
    {
        fprintf(stderr, "log_impl_t::init Failed to init log file info\n");

		return -1;
    }

    return start();
}

void log_impl_t::stop()
{
    log_queue_data_t log_queue_data(LOG_QUEUE_QUIT, LOG_LEVEL_OFF, "");
    _log_queue.push(log_queue_data);

    // wait for all jobs finish
    ::pthread_join(_pthread_id, NULL);
}

int log_impl_t::doLog(log_level_e log_level_, const char* fmt_, ...)
{
    if (log_level_ > _log_level)
    {
        return -1;
    }

    char buff[1024];
    va_list arg_list;
    va_start(arg_list, fmt_);
    vsnprintf(buff, sizeof(buff), fmt_, arg_list);
    va_end(arg_list);

    log_queue_data_t log_queue_data(LOG_QUEUE_DATA, log_level_, buff);
    if (_log_queue.push(log_queue_data))
    {
        fprintf(stderr, "log_impl_t::doLog Failed to push log to queue\n");
        return -1;
    }

    return 0;
}

int log_impl_t::initFileInfo()
{
    time_t time = ::time(NULL);
    struct tm* tm = localtime(&time);
    char buff[1024];

    _current_file = _file_path;
    if ('/' != _current_file[_current_file.length() - 1])
    {
        _current_file.append("/");
    }
    _current_file.append(_file_name);
    _fp = fopen(_current_file.c_str(), "ab+");
    if (NULL == _fp)
    {
        fprintf(stderr, "log_impl_t::initFileInfo Failed to open log file: %s\n", _current_file.c_str());
		return -1;
	}

    // get current file size
    struct stat stat_info;
    stat(_current_file.c_str(), &stat_info);
    _current_file_size = stat_info.st_size;

    // get current file index
    _current_file_index = 0;
    snprintf(buff , sizeof(buff), "%s.%d-%02d-%02d.%02d",
        _current_file.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, _current_file_index);
    while (0 == access(buff, F_OK))
    {
        snprintf(buff , sizeof(buff), "%s.%d-%02d-%02d.%02d",
            _current_file.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, ++_current_file_index);
    }

    // get current days
    _current_year = tm->tm_year + 1900;
    _current_month = tm->tm_mon + 1; 
    _current_day = tm->tm_mday;

    //fprintf(stdout, "log_impl_t::initFileInfo result: %s|%lu|%u|%u|%u|%u\n",
    //    _current_file.c_str(), _current_file_size, _current_file_index, _current_year, _current_month, _current_day);

    return 0;
}

int log_impl_t::start()
{
	int ret = pthread_create(&_pthread_id, NULL, ThreadProc, this);

	if (0 > ret)
	{
        fprintf(stderr, "log_impl_t::start Failed to create work thread and msg: %s\n", strerror(errno));
		return -1;
	}

    //fprintf(stdout, "log_impl_t::start Create log work thread success and thread id: %u\n", _pthread_id);
	return 0;
}

void* log_impl_t::ThreadProc(void* agrs_)
{
	log_impl_t* self = static_cast<log_impl_t*>(agrs_);
	self->run();

	return NULL;
}

int log_impl_t::run()
{
	_running = true;
	int ret = -1;
    log_queue_data_t log_queue_data(LOG_QUEUE_QUIT, LOG_LEVEL_OFF, "");

    //fprintf(stdout, "log_impl_t::run log thread start and wait for log\n");
	while (_running)
	{
		ret = _log_queue.pop(log_queue_data, _timeout);
		switch(ret)
		{
			case 0:
                // get exit cmd
                if (LOG_QUEUE_QUIT == log_queue_data._type)
                {
                    fclose(_fp);
                    _fp = NULL;
				    _running = false;
                }
                else
                {
                    // write log to file
				    doLogInter(log_queue_data);
                }
				break;
			case ETIMEDOUT:
                //fprintf(stdout, "log_impl_t::run thread queue timeout\n");
				break;
			case -1:
                fprintf(stderr, "log_impl_t::run thread queue return error\n");
				break;
			default:
                fprintf(stderr, "log_impl_t::run thread queue unknown return type: %d\n", ret);
				break;
		}
	}

	return 0;
}

int log_impl_t::doLogInter(const log_queue_data_t& log_queue_data_)
{
    if (checkFileRoll())
    {
        fprintf(stderr, "log_impl_t::run failed to checkFileRoll\n");

        return -1;
    }

    char buff[2048];
    int len = 0;
    if (_log_fmt & LOG_FMT_DATE)
    {
        time_t time = ::time(NULL);
        struct tm* tm = localtime(&time);
        len += snprintf(buff + len , sizeof(buff) - len, "%d-%02d-%02d %02d:%02d:%02d", 
            tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
    }
    if (_log_fmt & LOG_FMT_MSECOND)
    {
        struct timeval tv;
        gettimeofday(&tv, 0);
        len += snprintf(buff + len , sizeof(buff) - len, ".%d", tv.tv_usec);
    }
    if (_log_fmt & LOG_FMT_LEVEL)
    {
        len += snprintf(buff + len , sizeof(buff) - len, " %s", log_level_str[log_queue_data_._level]);
    }
    
    len += snprintf(buff + len , sizeof(buff) - len, "  %s", log_queue_data_._data.c_str());
    _current_file_size += len;

    if (EOF == fputs(buff, _fp))
    {  
        fprintf(stderr, "log_impl_t::doLogInter fail to fputs to log file");

        return -1;
    }

    if (EOF == fflush(_fp))
    {  
        fprintf(stderr, "log_impl_t::doLogInter fail to fflush to log file");

        return -1;
    }

    return 0;
}

int log_impl_t::checkFileRoll()
{
    char new_file_name[1024];
    time_t time = ::time(NULL);
    struct tm* tm = localtime(&time);

    // checks whether it need change days
    if (_current_year != tm->tm_year + 1900 ||
        _current_month != tm->tm_mon + 1 ||
        _current_day != tm->tm_mday)
    {
        if (0 != _current_file_size)
        {
            fclose(_fp);
            _fp = NULL;

            snprintf(new_file_name, sizeof(new_file_name), "%s.%d-%02d-%02d.%02d",
                _current_file.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday - 1, _current_file_index);

            if (rename(_current_file.c_str(), new_file_name))
            {
                fprintf(stderr, "log_impl_t::checkFileRoll Failed to rename log file: %s to %s\n",
                    _current_file.c_str(), new_file_name);
		        return -1;
	        }

            _fp = fopen(_current_file.c_str(), "ab+");
            if (NULL == _fp)
            {
                fprintf(stderr, "log_impl_t::checkFileRoll Failed to open log file: %s\n", _current_file.c_str());
		        return -1;
	        }
        }

        _current_file_size = 0;
        _current_file_index = 0;    // from 0
        _current_year = tm->tm_year + 1900;
        _current_month = tm->tm_mon + 1; 
        _current_day = tm->tm_mday;
    }

    // checks whether it need change file for size limitation
    if (_current_file_size >= _file_max_size)
    {
        fclose(_fp);
        _fp = NULL;

        snprintf(new_file_name, sizeof(new_file_name), "%s.%d-%02d-%02d.%02d",
            _current_file.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, _current_file_index);
        if (rename(_current_file.c_str(), new_file_name))
        {
            fprintf(stderr, "log_impl_t::checkFileRoll Failed to rename log file: %s to %s\n",
                _current_file.c_str(), new_file_name);
	        return -1;
        }

        _fp = fopen(_current_file.c_str(), "ab+");
        if (NULL == _fp)
        {
            fprintf(stderr, "log_impl_t::checkFileRoll Failed to open log file: %s\n", _current_file.c_str());
	        return -1;
        }

        _current_file_size = 0;
        _current_file_index++;
    }

    return 0;
}

} // namespace trident

